多媒体前端技术入门指南
随着B站、抖音、快手、淘宝直播等直播视频平台的快速崛起,前端衍生出了多媒体技术方向,各公司的传统前端团队里陆续出现了一支新军:Web多媒体团队。光看团队Title,这应该是一个拥有前端×多媒体交叉领域稀有技能的群体。阿里巴巴内部也存在众多Web多媒体团队,在内部我们针对新人整理了一份多媒体前端技术入门指南,今天将这份入门指南也对外分享出来,让大家了解我们近几年在该领域有哪些方向的工作,有哪些实践和落地,本文将从以下几个方面带着大家一窥究竟:
什么是多媒体前端?
W3C标准的媒体技术
播放场景和解决方案
面向消费:直播视频里的业务体系
面向生产:直播推流、视频剪辑等工具
阿里巴巴前端委员会多媒体方向的发展和规划
利用专业的前端能力,解决多媒体场景下各类技术和业务问题的前端,称之为多媒体前端。目前的多媒体前端主要从两类人群转化而来,一类是学数字媒体专业后从事前端开发的,一类是专业做前端后再学习多媒体的。这是两个成熟的知识体系交叉碰撞后的一个新的工作领域,需要具备高度还原、体验把控、跨端、工程化等前端开发能力,又要具有音视频基础、流媒体协议、播放技术、Web媒体技术等多媒体能力,目前这类人才的缺口还非常大。
最开始接触多媒体前端开发,一般场景比较简单,比如在网页上播放一个视频/音频,实现基础的播放控制(播放、暂停、进度条、音量大小、静音等)。在HTML5标准之前如果要在浏览器里播放视频内容,需要使用Adobe Flash或微软的Silverlight等插件,但是插件存在需要用户安装的不便捷和不安全等问题,因此W3C的HTML5标准中定义了一系列新的媒体元素来避免使用插件。以下是媒体开发者常用的HTML元素:
HTML元素
<video>
标签用于播放视频或直播流,可以通过JS HTMLVideoElement对象访问,结合媒体API和其他DOM技术可实现更丰富的媒体应用<audio>
标签用于播放音频,可以通过JS HTMLAudioElement对象访问<source>
标签放在<audio>
或者<video>
内部,以指定播放的媒体源,可以添加多个不同格式、大小、分辨率的媒体源,通过JS HTMLSourceElement对象访问<track>
标签放在<audio>
或者<video>
内部,在媒体播放时提供 WebVTT 格式化字幕或标题轨道。可以通过JS HTMLTrackElement对象访问
浏览器提供的媒体播放功能都是相当简单的,一个 video 或者 audio 标签再加上src就搞定了,但这缺少了诸如视频分段加载、视频码率切换、部分加载、内存管理等现代播放器应该有的功能,需要更定制化的播放需求,或者更丰富的多媒体应用,就要使用媒体API:
媒体API
媒体源扩展API
MSE (Media Source Extension) 扩展了浏览器的媒体播放功能,允许用JS动态构造媒体流MediaSource对象,然后喂给
<video>
和<audio>
标签进行更精细化的播放控制。也可以用JS把一些不支持的视频流格式转封装为支持的格式,B站开源的flv.js就是这个技术的一个典型实现,使用MSE技术将FLV源用JS实时转封装成HTML5支持的视频格式。更多信息会在下一节播放场景和解决方案中介绍。网络音频 API
Web Audio API 用于处理和合成Web应用程序中的音频,允许开发者进行声音合成、添加音频特效、音频可视化等,使用Web Audio API 我们几乎可以完成一个专业的Web音频处理软件(如节拍器、调音器等)。
媒体捕获和流媒体API
Media Stream API 让开发者可以使用本地摄像头和麦克风来采集录制音视频,或者捕获电脑屏幕,或者读取本地视频做合成,常用于Web摄像头、拍照、录屏、视频通话等,下文视频剪辑章节也有1688使用该技术实现web视频剪辑的案例。MediaStream 是连接 WebRTC API 和底层物理流的中间层,WebRTC将音视频经过Vocie / Video engine进行处理后,再通过MediaStream API暴露给上层使用。
WebRTC
WebRTC 是一套支持浏览器进行实时音视频对话的 W3C Javascript API,它包括了音视频的采集、编解码、网络传输、显示等功能,使互联网上任意两位用户在无需服务器的情况下实现实时的音频、视频和任意数据的通信。在2010年左右,实时通信只能使用专有软件、插件或Adobe Flash进行;2013年,Chrome 和 Firefox 之间进行了首次跨浏览器视频通话,开启浏览器之间无插件化的音视频通话的序幕。现在WebRTC的使用场景非常丰富,在音视频通信、直播推流、云剪辑、云游戏等场景都可以看到WebRTC的使用。
除了这些媒体API,还有一些技术通常与媒体API共同使用:
与媒体 API 共同使用的技术
Canvas API
Canvas API 允许在
<canvas>
上绘画、操纵并改变图像内容。这样可以与媒体以多种方式使用,包括设置<canvas>
元素作为视频播放或摄像头捕获的节点以捕获或操纵视频帧。
WebGL
WebGL 在已存在的 Canvas API 上提供了与 OpenGL ES 兼容的 API,使得在 Web 上制作强大的 3D 图像成为可能。通过一张画布,用于为媒体内容添加 3D 图像。
WebVR
Web Virtual Reality API 支持虚拟现实 (VR) 设备,使开发人员能够将用户的位置和移动转换为 3D 场景中的移动,然后在设备上显示。WebVR 有望逐渐被 WebXR 所取代,后者涵盖了更广泛的用例。
WebXR
WebXR 旨在最终取代 WebVR,是一种支持创建虚拟现实 (VR) 和增强现实 (AR) 内容的技术。混合现实内容可以显示在设备的屏幕上,或者是显示在眼镜或耳机内。
前文提到浏览器里通过<video>
就能实现视频播放了,那还需要我们前端做些什么呢?其实<video>
也存在很多限制,我们要先从媒体内容的封装格式和编码格式说起。
媒体内容源文件都是比较大的,为了便于传输和存储,需要对原视频文件通过编码来压缩文件大小,再通过容器封装将压缩后的视音频、字幕组合到一个容器内,这就是 编码 和 容器封装 的过程(可以用 压缩饼干 和 封袋包装 来理解,会出现很多不同的压缩工艺和包装规格)。
那么在播放端进行播放时,就要进行相应的 解封装 和 解码 (先拆开包装拿出饼干,享用饼干可以直接吃、也可以碾碎了吃、也可以泡着牛奶吃)。浏览器自带的播放器<video>
标签拥有解封装和解码功能,但对媒体内容的格式是有所限制的(浏览器只会拆开特定的包装方式,只能消化特定的饼干吃法)。那浏览器碰到“不会拆、不消化的饼干”,我们要怎么喂给<video>
呢?
除了容器格式、编码格式,还有流媒体协议、渲染容器、多实例播放等等问题需要多媒体前端一一解决,下面来分别介绍:
多协议、多容器格式
随着流媒体业务发展,出现了很多新的传输协议,媒体内容进一步包含在一层传输协议中(以HLS协议为例,增加了m3u8索引文件,并将源文件内容分片后封装到了一个个 TS 容器格式中),这样<video>
就无法识别了。要支持多协议、多容器格式的播放,开发者可以通过 MSE 来帮助浏览器识别并处理,将媒体内容 转封装 成可识别的容器格式(如MP4),这样<video>
就可以识别并播放各种媒体内容了。
上文提到的B站的flv.js以及社区的hls.js都是利用 MSE 来解决多协议、多容器格式的播放器库。
1. flv.js
flv.js是Bilibili网站开源的HTML5 flv播放器,基于HTTP-FLV流媒体协议,通过纯JS实现FLV转封装,使flv格式文件能在Web上进行播放。
但是flv.js也不是所有的flv格式视频都能播放,并且对浏览器环境也有一定的要求,以下是flv.js的使用限制:
视频必须是AVC(H.264)编码,音频必须为AAC或者MP3编码
浏览器环境必须支持MSE,查看支持列表:https://caniuse.com/#feat=mediasource,值得注意的是,ie浏览器中,ie11以上才能正常使用,而ie11浏览器必须在win8系统以上才能运行;移动端上,ios仅支持在iPadOS 13以上系统,ios手机端完全不可运行;android则要求4.4以上系统
浏览器必须支持video,fetch api、xhr和websocket支持其一便可
2. hls.js
hls.js是基于Http Live Stream协议开发,利用Media Source Extension,用于实现HLS在web上播放的一款js播放库。
由于HLS协议由苹果提出,并且在移动端设备上广泛支持,因此可以被广泛应用于直播场景。而hls.js在pc端只需要支持MSE便可以应用,移动端使用原生video标签设置src便可完成播放。hls.js会先请求m3u8文件,然后读取到文件的分片列表,以及视频的编码格式、时长等。随后会按照顺序去对TS分片进行请求,然后借助MSE将二进制buffer内容进行合流,组成一个可播的媒体资源文件。
阿里内部也有多个团队有类似播放器产品,如阿里云的Aliplayer、淘系的VideoX、优酷的KPlayer,实现思路都基本一致。
3. 阿里云 Aliplayer
Aliplayer 经过几个版本的迭代演进,整个架构更加合理,赋予本身和用户更多的扩展能力,具有独立增加播放类型和功能的能力,比如要h5支持flv的播放能力,只需要新增Flv Extend功能扩展模块,而不用修改其他模块的代码,比如HLS Extend等等,确保不影响其他功能的正常工作,保持每个版本发布的稳定性。
4. 淘系 VideoX
Videox 底层的播放层也经历了几次变化,从最开始简单的 <video>
标签,到通过MSE来扩展各种格式的<video>
标签,到通过WebAssembly来对编解码格式进行扩展的<canvas>
+ Web Audio API的方式,未来可能还有底层通信的扩展及上层互动能力的增强。
5. 优酷 KPlayer
KPlayer 目前的方案拆分的较细,包括多个库和组件,主要有KMux 转封装库、KDRM WebDRM插件、KCTRL 播放器控制行为的核心抽象、MediaEngine 解码&渲染&播放的核心抽象、KUI 嵌入式UI框架。KPlayer播放核心设计如下:
多编码格式
上一小节解决了浏览器<video>
不支持的协议和格式(不会拆饼干包装)的问题,那遇到不支持的编码格式(不能消化的压缩工艺)该怎么办呢?
新的视频编码标准H.265、AV1等比传统H.264拥有更高压缩率,但浏览器<video>
本身并不支持,而业务为了降低成本都全链路切换新的编码格式如H.265,那多媒体前端就要自己实现浏览器端的JS播放器。由于Javascript是一种动态解释型语言,性能比C++等语言差很多,处理多媒体数据时性能短板就体现出来,WebAssembly的出现解决了Javascript的性能短板,极大扩展浏览器端的多媒体处理能力和场景。
对于Web端H.265播放器,阿里内部有多个团队都进行了相应的尝试,包括优酷、阿里云、淘系、ICBU等,思路基本一致,都是通过FFmpeg + Webassembly来实现一个JS播放器,使用JS拉流、解封装,将FFmpeg的265解码能力编译成wasm模块供JS调用,视频经过FFmpeg解码出来YUV帧数据,通过WebGL绘制图像帧数据,而音频方面因为浏览器支持AAC、MP3等主流音频格式,音频数据直接通过Web Audio API进行播放。设计思路如下:
多渲染容器
上面描述的场景主要是PC的Web浏览器,但我们更多的业务场景在移动端,面向消费者的场景对播放的体验和性能要求非常高,于是引入了播放器的跨端支持问题,尤其是跨渲染容器(Webview/Weex/小程序)。端侧的播放器主要使用Native播放器,前端封装成Weex/同层渲染组件,在各渲染容器中运行。
在端侧如果采用原生video方案,存在兼容性及性能问题,播放器本身占用内存高,业务不规范使用会带来稳定性风险,甚至导致APP Crash。以端内H5为例,采用的是和Rax团队、客户端基础团队、客户端播放器团队一起打造的同层渲染方案:
Native播放器:负责播放器的基础能力,播放器内核fetch流地址后,解封装(demux)后解码(decode),然后输出到上层Naitve同层组件层,Native播放器提供了点播/直播能力
Native同层组件:为底层播放器实例封装,主要负责联通通信连接层,并控制渲染
通信连接层:通过windmix管理渲染通道,再通过windvane绑定事件,传递属性,api能力调动,连接到前端播放器
前端播放器核心:担任前端播放器输出,将下层提供的事件/api/属性抹平成W3C制定的播放器标准,同时负责播放器稳定性相关能力
业务播放器封装:对下层的前端播放器核心封装播控协议,通过事件中心,管理各个播放器播放能力;同时从业务角度出发,对网络和设备性能进行监测,判断是否降级
多实例控制
前面提到的都是单个视频播放的场景,实际业务中还有同一个页面中多个播放器的场景,比如列表流、版头等,这时候就引入了播放器多实例控制的问题,需要保证页面内只有一个播放器播放(播放甜区)、内存管理等。
事件中心驱动 播放器内部维护一份待播放队列,由rax-view的onAppear和onDisappear实现,当执行onAppear时,待播放队列将对应播放器唯一id存入到队列中,当对应播放器执行onDisappear时,从队列中剔除 队列中的播放形式主要分为3种:1,滚动播放;2,指定id播放;3,onAppear场景播放;均由事件管控。再由事件中心分发到不同播放器,告知哪个播放器可播放,哪个不可播放 播放器存在于pcom库,无论是对于会场场景或是源码开发场景,均可保证事件中心通知到所有播放器 播控调度 播放器实例内存占用大概在20-40MB,对于一个H5页面来说,内存占用过高,极容易引发页面内存占用过大,导致crash等严重问题。所以在事件驱动播放同时,事件中心会保证有且仅有一个播放器实例正在播放
Web 直播间架构
Hybrid 直播间架构
统一规范的组件消息协议:包括组件包名、组件行为、业务自定义字段等 支持动态加载:直播间不同于其他详情页,互动的发送依赖主播操作,也依赖用户进入直播间的时机,每个用户参与到的互动可能都不一样,所以互动组件的动态加载对首屏性能很关键 缓存及依赖去重:同一个互动,主播可以多次推送,各个互动依赖的基础库(rax-xxx、universal-xxx)也存在较多重复,所以设计合理的缓存和依赖去重机制对性能提升也很重要 HOC高阶组件:直播间里的业务开发不同于其他独立的源码页面,比如直播间数据获取、消息和事件监听、横竖屏状态获取、带小窗跳转、直播观看时长等等都依赖直播间环境或者客户端API,业务组件都需要这些基础能力,需要通过HOC来增强业务组件
def 套件:直播间组件开发脚手架,增强调试能力,包括直播间模拟、调试代理、热更新、编译检测等功能 直播间 Debug 工具:基于直播容器开发一个Debug组件,提供日志调试、容器化API调用、数据Mock、消息Mock等功能 VS Code 插件:直播间Debug工具在PC端的同等方案,结合模拟器可以独立在PC端开发调试
<video>
标准。小程序直播间架构
直播插件:插件里除了包含最基本的播放器、无限列表、官方组件,还封装了组件布局规则、事件中心、容器api模块等基础能力 跨端通信层:由于插件和小程序宿主实例之间的js上下文完全隔离,因此@alipay/armer-x借助了appx对特定函数的声明不做序列化的特性进行了插件和宿主小程序之间的通信桥接,如给插件 赋予函数onConnectEmitter,在appx里是不做会做序列化的,因此数据可以通过该函数给予插件通知信息 小程序组件:二方、三方定制的小程序组件,满足特定的接入规则就可以借助直播间的容器能力进行定制 小程序实例:在拥有了二方组件、直播插件后,我们可以将二者进行整合、打包构建,生成一个新的小程序实例,实例的生成方式可以通过IDE构建,也可以通过搭建平台,如:「闪蝶」,目前的策略是支付宝侧通过闪蝶的方式构建生成实例,而百川侧,则通过IDE上传,并且借助淘宝开发平台进行百川投放
直播推流
桌面客户端:采用Electron + OBS方案(OBS是一个用于录制和进行网络直播的自由开源软件包,OBS使用C和C++语音编写,提供实时源和设备捕获、场景组成、编码、录制和广播等)。在设计上,OBS不负责业务功能,仅作为一个纯推流SDK。通过IPC通信将OBS的接口封装成V8的接口,再通过CMake或者GYP打包成Node模块供前端调用。播放区域(大小、padding等)、元素的交互行为(旋转、缩放等)、推流参数等全部封装成接口暴露给前端。整个APP为Electron中的一个BrowerView,采用React支撑视图,Mobx管理数据,跨页面通信以及相应的数据流转、缓存和窗口管理都由Electron主进程进行管理,前端通过将webview窗口句柄提供给OBS进程来保证推流画面的流畅显示 淘宝直播主播工作台 和 1688直播伴侣 都是采用的这个方案: Web浏览器端:采用WebRTC方案,相对于桌面端推流清晰度低、性能吃紧。比如融媒体团队由于业务特性,不能使用第三方的OBS开源软件,选择了该方案,产出了PC网页采集推流混流SDK,支持摄像头、电脑画面和垫片推流
视频剪辑
桌面端剪辑:Electron方案,GUI前端在Webview,编辑内核在Native,打包成Node模块供前端调用(和上述Electron直播推流类似)。采用该方案的有淘系的Marvel剪辑工具,用于亲拍业务 纯Web剪辑:浏览器方案,GUI前端和编辑内核都在浏览器。编辑内核提供的编辑、渲染、合成等能力通过 Media Stream API 或 者 FFmepg + WebAssembly 来实现。采用该方案的有1688视频编辑器(幻雀)、阿里小蜜的小蜜视频创作工具,以小蜜的方案为例: Web云剪辑:浏览器+服务端方案,GUI前端在浏览器,编辑内核在服务端。因为GUI前端和编辑内核分开在两端,所以还需要解决GUI如何呈现编辑内核内容的问题。通常有浏览器主动拉取渲染效果和WebRTC直播串流推给用户界面两种方案,后者的延时较低,体验更好。采用云剪辑方案的有阿里妈妈的Aliwood、盒马的盒作社视频编辑器、淘系的PC淘拍、蚂蚁的FMS短视频生产工具,以Aliwood的方案为例: 但目前采用Web云剪辑都是浏览器主动拉取渲染效果的方案,笔者还没有看到WebRTC的方案(类似云游戏),目前淘系正在建设WebRTC的云剪辑方案。
玩法生产
往期推荐